<?php

namespace Swostar\Route;

use Exception;

class Route  
{
    protected static $instance = null;

    // 路由文件集合
    protected $routeMap = [];

    // 路由存储
    protected $routeArray = [];

    // 访问类型
    protected static $verbs = ['GET', 'POST', 'PUT', 'DELETE'];

    // 模块 http/websocket/...
    protected $module = '';

    public function __construct()
    {
        // 包含路由文件
        $this->routeMap = [
            'http' => app()->getBasePath() . '/route/http.php',
            'websocket' => app()->getBasePath() . '/route/webSocket.php'
        ];
    }

    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new static();
        }
        return self::$instance;
    }

    /**
     * get请求路由
     * @param mixed $uri 
     * @param mixed $action 
     * @return $this 
     */
    public function get($uri, $action)
    {
        return $this->addRoute(['GET'], $uri, $action);
    }

    /**
     * post请求路由
     * @param mixed $uri 
     * @param mixed $action 
     * @return $this 
     */
    public function post($uri, $action)
    {
        return $this->addRoute(['POST'], $uri, $action);
    }

    /**
     * any请求路由
     * @param mixed $uri 
     * @param mixed $action 
     * @return $this 
     */
    public function any($uri, $action)
    {
        return $this->addRoute(self::$verbs, $uri, $action);
    }

    public function ws($uri, $controller)
    {
        $actions = ['open', 'message', 'close'];

        foreach ($actions as $action) {
            // 因为每个控制器均有上述三个方法，所以绑定路由时，需将'控制器@方法'补齐
            $this->addRoute([$action], $uri, $controller . '@'. $action);
        }
    }

    /**
     * 注册路由
     * @param array $methods 请求方式
     * @param string $uri 地址
     * @param mixed $action 执行方法
     * @return $this 
     */
    protected function addRoute(array $methods, string $uri, $action)
    {
        foreach ($methods as $method) {
            $this->routeArray[$this->module][$method][$uri] = $action;
        }
        return $this;
    }

    /**
     * 路由匹配
     * @param string $method 请求类型
     * @param string $path 请求路径
     * @param array  $params 额外参数
     * @return mixed 执行结果
     * @throws Exception 
     */
    public function match(string $method, string $path, $params = [])
    {
        // 1. 获取请求的uripath
        // 2. 根据类型获取路由
        // 3. 根据请求的uri 匹配 相应的路由；并返回action
        // 4. 判断执行的方法的类型是控制器还是闭包
        //    4.1 执行闭包
        //    4.2 执行控制器

        $action = null;

        foreach ($this->routeArray[$this->module][$method] as $uri => $value) {
            // 判断路由头部是否添加 /
            $uri = ($uri && substr($uri, 0, 1) != '/') ? '/' . $uri : $uri;

            // 获取请求方法
            if ($path === $uri) {
                $action = $value;
                break;      
            }
        }

        if (!empty($action)) {
            return $this->returnAction($action, $params);     
        }

        throw new Exception('未找到该方法', 404);
        // return '404';
    }

    /**
     * 控制器、方法解析，执行操作
     * @param mixed $action 控制器/方法--字符串
     * @return mixed 执行结果
     */
    protected function returnAction($action, $params)
    {
        // 闭包
        if ($action instanceof \Closure) {
            return $action(...$params);
        }

        $actionNamespace = '\app\\' . $this->module . '\controller\\';

        // 控制器解析  格式：Index@aaa  index控制器  aaa方法
        list($controller, $action) = explode('@', $action);
        $controller = $actionNamespace . $controller;

        return (new $controller())->$action(...$params);
    }

    public function setModule($module)  
    {
        $this->module = $module;
        return $this;
    }

    /**
     * 引入路由文件
     * @return $this 
     */
    public function registerRoute()
    {
        foreach ($this->routeMap as $key => $path) {
            $this->module = $key;  // 设置模块名，针对不同模块配置
            require_once $path;
        }
        return $this;
    }

    public function getRoutes()
    {
        return $this->routeArray;
    }
}
